/*	Brian Kirkpatrick
	QuX class
	Begun 6/30/2010

	Queried XML management for low-level languages
*/

#include "QuX.h"

// --- Constructors --- //

QuX::QuX() {
	// Initially blank
	data = NULL;
	error = "";
}

QuX::~QuX() {
	// Free data if not blank
	if (data != NULL) {
		delete data;
		data = NULL;
	}
}

// --- Private --- //

bool QuX::isWhitespace(char c) {
	return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

bool QuX::parse(FILE * file, stringTree * parent, int level, TreeType state, char carryover) {
	// Iterate through file contents from current location, adding children to *parent and recursing into child nodes
	// Rules for parsing:
	//  - Each call enteres on a certain character (carryover), which has been parsed to determine the appropriate state
	//  - Some states will have children, recursively parsing using the new child as the parent parameter
	//  - When a parse is returned, no more siblings exist on the same level
	//  - Each parse continues on to subsequent sibilings if not completed, passing parent as parent
	//  - Errors record messages and return false; messages are given at the lowest possible level of recursion
	//  - After a clause has been parsed, siblings may be parsed by handing off to parse with TT_UNKNOWN and same parent
	//  - Return true ONLY when no more siblings exist (</> reached, parent ended) or end of file
	stringTree * child = NULL;
	string buffer = "";
	switch (state) {
		case TT_ATTRIBUTE: {
			// Enter on text
			// Recurse into values on ="
			// Return on />, ?>
			// Continue on text, >
			// Error on <, ", /, ?, EOF
			while (!isWhitespace(carryover) && carryover != '=' && carryover != '"' && carryover != EOF) {
				buffer += carryover;
				carryover = fgetc(file);
			}
			if (carryover == '"') {
				error = "Invalid \" mark in attribute " + buffer;
				return false;
			}
			if (carryover == EOF) {
				error = "EOF reached in attribute " + buffer;
				return false;
			}

			// Create attribute node from buffer
			child = new stringTree(buffer, TT_ATTRIBUTE);
			parent->addChild(child);

			// Recurse to value
			while (isWhitespace(carryover) && carryover != EOF) {
				carryover = fgetc(file);
			}
			if (carryover == EOF) {
				error = "EOF reached while search for value of attribute " + buffer;
				return false;
			}
			if (carryover != '=') {
				error = "Invalid characters following attribute " + buffer + "; value expected";
				return false;
			}
			do {
				carryover = fgetc(file);
			} while (isWhitespace(carryover) && carryover != EOF);
			if (carryover == EOF) {
				error = "EOF reached while search for value of attribute " + buffer;
				return false;
			}
			if (carryover != '"') {
				error = "Invalid characters following attribute " + buffer + "; value expected";
				return false;
			}
			if (!parse(file, child, level+1, TT_VALUE, carryover)) {
				return false;
			}

			// Check for sibling attributes
			do {
				carryover = fgetc(file);
			} while (isWhitespace(carryover) && carryover != EOF);
			if (carryover == EOF) {
				error = "EOF reached while search for value of attribute " + buffer;
				return false;
			} else if (carryover == '?' || carryover == '/') {
				carryover = fgetc(file);
				if (carryover == EOF) {
					error = "EOF reached while search for value of attribute " + buffer;
					return false;
				}
				if (carryover == '>') {
					return true;
				} else {
					error = "Attribute " + buffer + " ended with invalid element closure";
					return false;
				}
			} else if (carryover == '>') {
				// Continue parsing for child nodes
				do {
					carryover = fgetc(file);
				} while (isWhitespace(carryover));
				if (carryover == EOF) {
					error = "End of file found with element still open";
					return false;
				} else if (carryover == '<') {
					return parse(file, parent, level, TT_ELEMENT, carryover);
				} else {
					return parse(file, parent, level, TT_TEXT, carryover);
				}
			} else {
				return parse(file, parent, level, TT_ATTRIBUTE, carryover);
			}
			break;
						   }
		case TT_VALUE: {
			// Enter on "
			// No recursion
			// Return on "
			// No continuation
			// Error on EOF
			carryover = fgetc(file);
			do {
				buffer += carryover;
				carryover = fgetc(file);
			} while (carryover != '"' && carryover != EOF);
			if (carryover == EOF) {
				error = "EOF reached while parsing attribute value";
				return false;
			}
			child = new stringTree(buffer, TT_VALUE);
			parent->addChild(child);
			return true;
			break;
					   }
		case TT_TEXT: {
			// Enter on text
			// No recursion
			// Return on <
			// No continuation
			// Error on >, EOF
			while (isWhitespace(carryover) && carryover != EOF) {
				carryover = fgetc(file);
			}
			do {
				buffer += carryover;
				carryover = fgetc(file);
			} while (carryover != '<' && carryover != '>' && carryover != EOF);
			if (carryover == EOF) {
				// End of file
				error = "End of file reached in text node " + buffer;
				return false;
			} else if (carryover == '>') {
				// Invalid bracket
				error = "Bracket close attempted in text node " + buffer;
				return false;
			} else {
				// Buffer contents are text field; bracket begins sibling node
				child = new stringTree(buffer, TT_TEXT);
				parent->addChild(child);
				return parse(file, parent, level, TT_ELEMENT, carryover);
			}
			break;
					  }
		case TT_COMMENT: {
			// Enter on <!
			// No recursion
			// Return on -->
			// Continuation on all
			// Error on EOF
			carryover = fgetc(file);
			if (carryover != '-') {
				error = "Incorrectly-formatted comment";
				return false;
			}
			carryover = fgetc(file);
			if (carryover != '-') {
				error = "Incorrectly-formatted comment";
				return false;
			}

			char next1 = fgetc(file);
			char next2 = fgetc(file);
			while (carryover != '-' || next1 != '-' || next2 != '>') {
				carryover = next1;
				next1 = next2;
				next2 = fgetc(file);
				if (carryover != '-' || next1 != '-' || next2 != '>') {
					buffer += carryover;
				}
				if (carryover == EOF || next1 == EOF || next2 == EOF) {
					error = "End of file reached in comment " + buffer;
					return false;
				}
			}
			child = new stringTree(buffer, TT_COMMENT);
			parent->addChild(child);
			do {
				carryover = fgetc(file);
			} while (isWhitespace(carryover));
			TreeType nextState = carryover == '<' ? TT_ELEMENT : TT_TEXT;
			return parse(file, parent, level, nextState, carryover);
			break;
						 }
		case TT_DECLARATION: {
			// Enter on <?
			// Recursion on text past xml
			// Return on ?>
			// Continuation on all
			// Error on <, >, EOF
			if (level != 0) {
				error = "Declarations must exist at root level";
				return false;
			}
			carryover = fgetc(file);
			char next1 = fgetc(file);
			char next2 = fgetc(file);
			if (carryover != 'x' || next1 != 'm' || next2 != 'l') {
				error = "Incorrectly formatted declaration";
				return false;
			}
			child = new stringTree("", TT_DECLARATION);
			parent->addChild(child);

			do {
				carryover = fgetc(file);
			} while (isWhitespace(carryover) && carryover != EOF);
			if (carryover == EOF) {
				error = "EOF reached while parsing declaration";
				return false;
			}
			if (carryover == '"' || carryover == '=' || carryover == '/') {
				error = "Invalid character in declaration";
				return false;
			}

			bool result = parse(file, child, level+1, TT_ATTRIBUTE, carryover);
			if (!result) {
				return false;
			}

			// Last char read should be ?>; return
			return true;
			break;
							 }
		case TT_ELEMENT: {
			// Enter on <
			// Recurse on text past tag
			// Return on </tag>
			// Continuation on all past >, />
			// Error on EOF, <, <>, </>
			do {
				carryover = fgetc(file);
			} while (isWhitespace(carryover));

			if (carryover == '/') {
				// Try to close element; check against parent value
				string parentValue = parent->getValue();
				for (unsigned int i = 0; i < parentValue.length(); i++) {
					if (fgetc(file) != parentValue[i]) {
						error = "Mis-matched element closing (parent was " + parentValue + ")";
						return false;
					}
				}
				do {
					carryover = fgetc(file);
				} while (isWhitespace(carryover));
				if (carryover != '>') {
					error = "Attributes within element closing for " + parentValue;
					return false;
				}
				return true;
			} else if (carryover == '?') {
				// Parse declaration
				return parse(file, parent, level, TT_DECLARATION, carryover);
			} else if (carryover == '!') {
				// Parse comment
				return parse(file, parent, level, TT_COMMENT, carryover);
			} else if (carryover == '>') {
				// Invalid
				error = "Element has no value";
				return false;
			} else {
				// New element! Grab name, create child, start parsing children recursively
				do {
					buffer += carryover;
					carryover = fgetc(file);
				} while (!isWhitespace(carryover) && carryover != '>' && carryover != '/');
				child = new stringTree(buffer, TT_ELEMENT);
				parent->addChild(child);

				while (isWhitespace(carryover) && carryover != EOF) {
					carryover = fgetc(file);
				} 
				if (carryover == EOF) {
					error = "End of file reached while parsing into attributes";
					return false;
				}

				if (carryover == '>') {
					if (!parse(file, child, level+1, TT_UNKNOWN)) {
						return false;
					}
					return parse(file, parent, level, TT_UNKNOWN);
				} else if (carryover == '/') {
					do {
						carryover = fgetc(file);
					} while (isWhitespace(carryover));
					if (carryover == '>') {
						return parse(file, parent, level, TT_UNKNOWN);
					} else {
						error = "Element contains stray /";
						return false;
					}
				} else {
					if (!parse(file, child, level+1, TT_ATTRIBUTE, carryover)) {
						return false;
					}
					return parse(file, parent, level, TT_UNKNOWN);
				}
			}
			break;
						 }
		case TT_UNKNOWN:
		default: {
			// Parse to first char and determine element vs text
			// Return on EOF
			while (isWhitespace(carryover)) {
				carryover = fgetc(file);
			}
			if (carryover == '<') {
				return parse(file, parent, level, TT_ELEMENT, carryover);
			} else if (carryover == '>') {
				error = "Closing bracket encountered; opening or text expected";
				return false;
			} else {
				return parse(file, parent, level, TT_TEXT, carryover);
			}
			break;
				 }
	}
	if (child != NULL) {
		parent->addChild(child);
	}
	return true;
}

string QuX::trimStr(string s) {
	int start = 0;
	while (isWhitespace(s[start])) {
		start++;
	}
	string toReturn = s.substr(start, s.length()-start);
	int end = toReturn.length() - 1;
	while (isWhitespace(toReturn[end])) {
		end--;
	}
	return toReturn.substr(0, end+1);
}

string QuX::lowerStr(string s) {
	for (unsigned int i = 0; i < s.length(); i++) {
		if (s[i] >= 'A' && s[i] <= 'Z') {
			s[i] = char(s[i] + 32);
		}
	}
	return s;
}

int QuX::search(string needle, string haystack) {
	// Returns the location of ther first character of needle in haystack, or -1 if not present
	int matchLength = 0;
	for (int unsigned i = 0; i < haystack.length(); i++) {
		if (needle[matchLength] == haystack[i]) {
			matchLength++;
		} else if (needle[0] == haystack[i]) {
			matchLength = 1;
		} else {
			matchLength = 0;
		}
		if (matchLength == needle.length()) {
			return i - matchLength + 1;
		}
	}
	return -1;
}

string QuX::intToStr(int n) {
	if (n == 0) return "0";
	bool isNegative = n < 0;
	string toReturn = "";
	int r = 0; char c = '0';
	while (n * n >= 1) {
		r = n - ((n / 10) * 10);
		r = r < 0 ? -r : r;
		c = (char)(r + 48);
		toReturn = c + toReturn;
		n = n / 10;
	}
	if (isNegative) toReturn = "-" + toReturn;
	return toReturn;
}

stringDict QuX::explodeStr(string s, string d, bool t) {
	// Explodes the string s using the delimiter d, returning an array of un-indexed strings (t determines trimming)
	stringDict toReturn = stringDict();
	int ndx1 = 0; int ndx2 = search(d, s); int i = 0;
	string key = "";
	while (ndx2 != -1) {
		key = intToStr(i);
		if (t) {
			toReturn.set(key, trimStr(s.substr(ndx1, ndx2-ndx1)));
		} else {
			toReturn.set(key, s.substr(ndx1, ndx2-ndx1));
		}
		ndx1 = ndx2 + d.length();
		ndx2 = search(d, s.substr(ndx1, s.length()-ndx1+1));
		if (ndx2 == -1) {
			key = intToStr(i+1);
			if (t) {
				toReturn.set(key, trimStr(s.substr(ndx1, s.length()-ndx1+1)));
			} else {
				toReturn.set(key, s.substr(ndx1, s.length()-ndx1+1));
			}
			return toReturn;
		}
		ndx2 += ndx1;
		i++;
	}

	// Null-loop case
	key = intToStr(i+1);
	if (t) {
		toReturn.set(key, trimStr(s.substr(ndx1, s.length()-ndx1+1)));
	} else {
		toReturn.set(key, s.substr(ndx1, s.length()-ndx1+1));
	}
	return toReturn;
}

string QuX::getValue(stringTree * node, string aettribute) {
	// Searches for the given aettribute in node (attribute value or element-child text value); returns "" if not there
	stringTree * children = node->getChild();
	while (children != NULL) {
		if (children->getType() == TT_ATTRIBUTE && children->getValue() == aettribute && children->getChild() != NULL && children->getChild()->getType() == TT_VALUE) {
			return children->getChild()->getValue();
		} else if (children->getType() == TT_ELEMENT && children->getValue() == aettribute && children->getChild() != NULL && children->getChild()->getType() == TT_TEXT) {
			return children->getChild()->getValue();
		}
		children = children->getNext();
	}
	return "";
}

quxResult QuX::select(stringTree * node, stringDict fields, stringDict sources) {
	// Performs initial selection of all fields from all relevant sources
	quxResult toReturn = quxResult();
	if (node->getType() == TT_TEXT || node->getType() == TT_VALUE) return toReturn;

	// If current node is in sources, look for fields
	if (sources.inDict(node->getValue())) { // Will need to improve to check for *, element._metafield
		stringDict newEntry = stringDict();
		for (int i = 0; i < fields.getSize(); i++) {
			newEntry.set(fields[i], getValue(node, fields[i])); // Simple copy will need to be modified to check for _metafields and aliased fields
		}
		toReturn = toReturn + newEntry;
	}

	// Recurse to siblings
	stringTree * sibling = node->getNext();
	if (sibling != NULL) {
		toReturn = toReturn + select(sibling, fields, sources);
	}

	// Recurse to child nodes
	stringTree * children = node->getChild();
	if (children != NULL) {
		toReturn = toReturn + select(children, fields, sources);
	}

	// Return results
	return toReturn;
}

quxResult QuX::selectQuery(string q) {
	// Query form: SELECT (fields) FROM (source) [WHERE conditions] [organization]
	// Determine divisions
	int fieldsNdx = search("select", q);
	int sourceNdx = search("from", q);
	int conditionsNdx = search("where", q);
	int organizationOrderNdx = search("order by", q);
	int organizationLimitNdx = search("limit", q);

	// SELECT and FROM are required
	if (fieldsNdx == -1) {
		error = "SELECT query missing SELECT";
		return quxResult();
	}
	if (sourceNdx == -1) {
		error = "SELECT query missing FROM";
		return quxResult();
	}

	// Determine fields
	string fieldsString = q.substr(fieldsNdx+6, sourceNdx - fieldsNdx-6);
	stringDict fields = explodeStr(fieldsString, ",", true);
//	printf("Fields: "); fields.print(); printf("\n");

	// Determine source
	int sourceLength = conditionsNdx == -1 ? q.length() - sourceNdx-4 : conditionsNdx - sourceNdx;
	string sourceString = q.substr(sourceNdx+4, sourceLength);
	stringDict sources = explodeStr(sourceString, ",", true);
//	printf("Sources: "); sources.print(); printf("\n");

	// Select initial set
	quxResult initialSet = select(data, fields, sources);

	// Return refined set
	return initialSet;
}

quxResult QuX::insertQuery(string q) {
	return quxResult();
}

quxResult QuX::updateQuery(string q) {
	return quxResult();
}

// --- Methods --- //

bool QuX::open(string f) {
	// Parse xml file contents into stringTree representation
	// Reset data for new attempt
	if (data != NULL) {
		delete data;
		data = NULL;
	}

	// Try to open file
	FILE * hFile;
	if (fopen_s(&hFile, f.c_str(), "r") != 0) return false;
	
	// Single-pass file parsing--amazing! Parse recursively for each node, passing parent
	data = new stringTree("__head", TT_ELEMENT);
	char carryover = fgetc(hFile);
	bool result = true;
	while (carryover != EOF && result) {
		result = parse(hFile, data, 0, TT_UNKNOWN, carryover);
		carryover = fgetc(hFile);
	}
	if (data->numChildren() > 0) {
		data = data->getChild();
	}

	// Finished; close
	fclose(hFile);
	return result;
}

bool QuX::write(string f) {
	// Write stringTree data into file (use filename if f is blank)
	return true;
}

quxResult QuX::query(string qry) {
	// Query existing data and return result (array of string dictionaries) if SELECT
	error = "";
	if (data == NULL) {
		error = "No XML data loaded; cannot query";
		return quxResult();
	}
	string q = lowerStr(trimStr(qry));
	string operation = q.substr(0, 6);
	if (operation == "select") {
		return selectQuery(q);
	} else if (operation == "update") {
		return updateQuery(q);
	} else if (operation == "insert") {
		return insertQuery(q);
	} else {
		error = "Unknown operator " + operation;
		return quxResult();
	}
}

string QuX::getError() {
	return error;
}

void QuX::print() {
	stringTree * target = data;
	while (target != NULL) {
		target->print();
		target = target->getNext();
	}
	if (data == NULL) {
		printf("No data loaded.\n");
	} else {
		printf("\n");
	}
}